<!DOCTYPE HTML>
<!--
	Dimension by HTML5 UP
	html5up.net | @ajlkn
	Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
 <head>
  <title>
   Dimension by HTML5 UP
  </title>
  <!-- <meta charset="utf-8" /> -->
  <!-- <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> -->
  <meta charset="utf-8"/>
  <meta content="width=device-width,initial-scale=1.0" name="viewport"/>
  <link href="../../assets/css/article.css" rel="stylesheet"/>
  <link href="https://cdn.bootcss.com/highlight.js/9.15.8/styles/github.min.css" rel="stylesheet"/>
  <noscript>
   <link href="../../assets/css/noscript.css" rel="stylesheet"/>
  </noscript>
 </head>
 <body>
  <div id="app">
  </div>
  <!-- built files will be auto injected -->
 </body>
 <body class="is-preload">
  <!-- Wrapper -->
  <div id="wrapper">
   <!-- Main -->
   <div id="main">
    <article id="article">
     <h1 id="soulnacos-bootstrap">
      Soul网关源码解析（十七）Nacos数据同步解析-Bootstrap端
     </h1>
     <hr/>
     <h2 id="_1">
      简介
     </h2>
     <p>
      基于上篇：
      <a href="https://juejin.cn/post/6920764643967238151">
       Soul网关源码阅读（十五）Zookeeper数据同步-Bootstrap端
      </a>
      ，这篇我们要研究下Nacos数据同步原理
     </p>
     <h2 id="_2">
      概览
     </h2>
     <p>
      Nacos数据同步和zookeeper数据同步非常相似，都是通过监听数据变化的手段来进行实现的，而且从Debug的数据接收情况来看，形式上表现的也是增量更新的
     </p>
     <p>
      但猫大人推荐的同步方式是websocket和zookeeper，原因是他们能增量更新，并没有提到nacos。在提到HTTP长轮询的时候，提了以后Nacos的实现，则猜测Nacos内部实现的方式是长轮询，所以不推荐Nacos数据同步方式。但这只是猜测，需要后面研究下Nacos才能验证，这里先大胆猜一下
     </p>
     <p>
      通过分析，Nacos的数据同步方式流程基本如下：
     </p>
     <ul>
      <li>
       1.构造Nacos相关服务
      </li>
      <li>
       2.启动监听目前的五种数据类型
      </li>
      <li>
       3.收到改动的时候调用相应的subscribe进行数据更新
      </li>
     </ul>
     <p>
      Nacos的数据更新代码有些令人疑惑，在数据变化收到数据的时候，先进行unSubscribe，再onSubscribe，相当的令人不解
     </p>
     <p>
      而且Nacos好像收不到数据删除通知
     </p>
     <p>
      具体解析过程请看源码Debug环节
     </p>
     <h2 id="_3">
      示例运行
     </h2>
     <p>
      2021.1.23日之前的Soul主分支版本Nacos同步存在问题，运行需要参考上篇：
      <a href="https://juejin.cn/post/6920764643967238151">
       Soul网关源码阅读（十五）Zookeeper数据同步-Bootstrap端
      </a>
      ,进行配置，配置运行后，我们进入源码Debug环节
     </p>
     <h2 id="debug">
      源码Debug
     </h2>
     <h3 id="_4">
      启动配置跟踪
     </h3>
     <p>
      首先寻找切入点，在之前的
      <a href="https://juejin.cn/post/6920596173925384206">
       Soul网关源码阅读（十二）数据同步初探
      </a>
      中，我们知道了Nacos数据同步的入口类是：
     </p>
     <ul>
      <li>
       soul-sync-data-nacos : NacosCacheHandler
      </li>
     </ul>
     <p>
      我们找到相应的类，其构造函数如下，我们看到了熟悉的Subscribe关键字，通过调用他们实现本地缓存的更新
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NacosCacheHandler</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="nf">NacosCacheHandler</span><span class="p">(</span><span class="kd">final</span> <span class="n">ConfigService</span> <span class="n">configService</span><span class="p">,</span> <span class="kd">final</span> <span class="n">PluginDataSubscriber</span> <span class="n">pluginDataSubscriber</span><span class="p">,</span>
                             <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">MetaDataSubscriber</span><span class="o">&gt;</span> <span class="n">metaDataSubscribers</span><span class="p">,</span>
                             <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">AuthDataSubscriber</span><span class="o">&gt;</span> <span class="n">authDataSubscribers</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="na">configService</span> <span class="o">=</span> <span class="n">configService</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">pluginDataSubscriber</span> <span class="o">=</span> <span class="n">pluginDataSubscriber</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">metaDataSubscribers</span> <span class="o">=</span> <span class="n">metaDataSubscribers</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">authDataSubscribers</span> <span class="o">=</span> <span class="n">authDataSubscribers</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">updatePluginMap</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">configInfo</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="c1">// Fix bug #656(https://github.com/dromara/soul/issues/656)</span>
            <span class="n">List</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="n">pluginDataList</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">GsonUtils</span><span class="p">.</span><span class="na">getInstance</span><span class="p">().</span><span class="na">toObjectMap</span><span class="p">(</span><span class="n">configInfo</span><span class="p">,</span> <span class="n">PluginData</span><span class="p">.</span><span class="na">class</span><span class="p">).</span><span class="na">values</span><span class="p">());</span>
            <span class="n">pluginDataList</span><span class="p">.</span><span class="na">forEach</span><span class="p">(</span><span class="n">pluginData</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">pluginDataSubscriber</span><span class="p">).</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">subscriber</span> <span class="o">-&gt;</span> <span class="p">{</span>
                <span class="n">subscriber</span><span class="p">.</span><span class="na">unSubscribe</span><span class="p">(</span><span class="n">pluginData</span><span class="p">);</span>
                <span class="n">subscriber</span><span class="p">.</span><span class="na">onSubscribe</span><span class="p">(</span><span class="n">pluginData</span><span class="p">);</span>
            <span class="p">}));</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">JsonParseException</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">log</span><span class="p">.</span><span class="na">error</span><span class="p">(</span><span class="s">"sync plugin data have error:"</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们在上面的构造函数上打上断点，看看其调用栈
     </p>
     <p>
      我们来到下面这个类，我们看到了其继承了NacosCacheHandler,构造好相应的数据后，start启动，在其函数中，我们看到了和zookeeper监听非常相似的函数，我们猜测这个函数就是监听的函数。依据前面的经验，在里面应该进行了一些初始化的工作后进行变化监听，我们后面具体调试看看。继续在构造函数上打上断点，再往上看看
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NacosSyncDataService</span> <span class="kd">extends</span> <span class="n">NacosCacheHandler</span> <span class="kd">implements</span> <span class="n">AutoCloseable</span><span class="p">,</span> <span class="n">SyncDataService</span> <span class="p">{</span>

    <span class="kd">public</span> <span class="nf">NacosSyncDataService</span><span class="p">(</span><span class="kd">final</span> <span class="n">ConfigService</span> <span class="n">configService</span><span class="p">,</span> <span class="kd">final</span> <span class="n">PluginDataSubscriber</span> <span class="n">pluginDataSubscriber</span><span class="p">,</span>
                                <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">MetaDataSubscriber</span><span class="o">&gt;</span> <span class="n">metaDataSubscribers</span><span class="p">,</span> <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">AuthDataSubscriber</span><span class="o">&gt;</span> <span class="n">authDataSubscribers</span><span class="p">)</span> <span class="p">{</span>

        <span class="kd">super</span><span class="p">(</span><span class="n">configService</span><span class="p">,</span> <span class="n">pluginDataSubscriber</span><span class="p">,</span> <span class="n">metaDataSubscribers</span><span class="p">,</span> <span class="n">authDataSubscribers</span><span class="p">);</span>
        <span class="n">start</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">start</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">watcherData</span><span class="p">(</span><span class="n">PLUGIN_DATA_ID</span><span class="p">,</span> <span class="k">this</span><span class="p">::</span><span class="n">updatePluginMap</span><span class="p">);</span>
        <span class="n">watcherData</span><span class="p">(</span><span class="n">SELECTOR_DATA_ID</span><span class="p">,</span> <span class="k">this</span><span class="p">::</span><span class="n">updateSelectorMap</span><span class="p">);</span>
        <span class="n">watcherData</span><span class="p">(</span><span class="n">RULE_DATA_ID</span><span class="p">,</span> <span class="k">this</span><span class="p">::</span><span class="n">updateRuleMap</span><span class="p">);</span>
        <span class="n">watcherData</span><span class="p">(</span><span class="n">META_DATA_ID</span><span class="p">,</span> <span class="k">this</span><span class="p">::</span><span class="n">updateMetaDataMap</span><span class="p">);</span>
        <span class="n">watcherData</span><span class="p">(</span><span class="n">AUTH_DATA_ID</span><span class="p">,</span> <span class="k">this</span><span class="p">::</span><span class="n">updateAuthMap</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">close</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">LISTENERS</span><span class="p">.</span><span class="na">forEach</span><span class="p">((</span><span class="n">dataId</span><span class="p">,</span> <span class="n">lss</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">{</span>
            <span class="n">lss</span><span class="p">.</span><span class="na">forEach</span><span class="p">(</span><span class="n">listener</span> <span class="o">-&gt;</span> <span class="n">getConfigService</span><span class="p">().</span><span class="na">removeListener</span><span class="p">(</span><span class="n">dataId</span><span class="p">,</span> <span class="n">GROUP</span><span class="p">,</span> <span class="n">listener</span><span class="p">));</span>
            <span class="n">lss</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span>
        <span class="p">});</span>
        <span class="n">LISTENERS</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      在上面的构造函数打上断点后，我们跟踪调用栈，来到熟悉的Spring配置，这里配置了Nacos相关的东西，构造nacosSyncDataService的时候启动了Nacos监听
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NacosSyncDataConfiguration</span> <span class="p">{</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="n">SyncDataService</span> <span class="nf">nacosSyncDataService</span><span class="p">(</span><span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">ConfigService</span><span class="o">&gt;</span> <span class="n">configService</span><span class="p">,</span> <span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">PluginDataSubscriber</span><span class="o">&gt;</span> <span class="n">pluginSubscriber</span><span class="p">,</span>
                                           <span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">MetaDataSubscriber</span><span class="o">&gt;&gt;</span> <span class="n">metaSubscribers</span><span class="p">,</span> <span class="kd">final</span> <span class="n">ObjectProvider</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">AuthDataSubscriber</span><span class="o">&gt;&gt;</span> <span class="n">authSubscribers</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"you use nacos sync soul data......."</span><span class="p">);</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">NacosSyncDataService</span><span class="p">(</span><span class="n">configService</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">(),</span> <span class="n">pluginSubscriber</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">(),</span>
                <span class="n">metaSubscribers</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">(</span><span class="n">Collections</span><span class="p">::</span><span class="n">emptyList</span><span class="p">),</span> <span class="n">authSubscribers</span><span class="p">.</span><span class="na">getIfAvailable</span><span class="p">(</span><span class="n">Collections</span><span class="p">::</span><span class="n">emptyList</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="nd">@Bean</span>
    <span class="kd">public</span> <span class="n">ConfigService</span> <span class="nf">nacosConfigService</span><span class="p">(</span><span class="kd">final</span> <span class="n">NacosConfig</span> <span class="n">nacosConfig</span><span class="p">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="p">{</span>
        <span class="n">Properties</span> <span class="n">properties</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Properties</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">nacosConfig</span><span class="p">.</span><span class="na">getAcm</span><span class="p">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">nacosConfig</span><span class="p">.</span><span class="na">getAcm</span><span class="p">().</span><span class="na">isEnabled</span><span class="p">())</span> <span class="p">{</span>
            <span class="n">properties</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">PropertyKeyConst</span><span class="p">.</span><span class="na">ENDPOINT</span><span class="p">,</span> <span class="n">nacosConfig</span><span class="p">.</span><span class="na">getAcm</span><span class="p">().</span><span class="na">getEndpoint</span><span class="p">());</span>
            <span class="n">properties</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">PropertyKeyConst</span><span class="p">.</span><span class="na">NAMESPACE</span><span class="p">,</span> <span class="n">nacosConfig</span><span class="p">.</span><span class="na">getAcm</span><span class="p">().</span><span class="na">getNamespace</span><span class="p">());</span>
            <span class="n">properties</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">PropertyKeyConst</span><span class="p">.</span><span class="na">ACCESS_KEY</span><span class="p">,</span> <span class="n">nacosConfig</span><span class="p">.</span><span class="na">getAcm</span><span class="p">().</span><span class="na">getAccessKey</span><span class="p">());</span>
            <span class="n">properties</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">PropertyKeyConst</span><span class="p">.</span><span class="na">SECRET_KEY</span><span class="p">,</span> <span class="n">nacosConfig</span><span class="p">.</span><span class="na">getAcm</span><span class="p">().</span><span class="na">getSecretKey</span><span class="p">());</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="n">properties</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">PropertyKeyConst</span><span class="p">.</span><span class="na">SERVER_ADDR</span><span class="p">,</span> <span class="n">nacosConfig</span><span class="p">.</span><span class="na">getUrl</span><span class="p">());</span>
            <span class="n">properties</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">PropertyKeyConst</span><span class="p">.</span><span class="na">NAMESPACE</span><span class="p">,</span> <span class="n">nacosConfig</span><span class="p">.</span><span class="na">getNamespace</span><span class="p">());</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">NacosFactory</span><span class="p">.</span><span class="na">createConfigService</span><span class="p">(</span><span class="n">properties</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nd">@Bean</span>
    <span class="nd">@ConfigurationProperties</span><span class="p">(</span><span class="n">prefix</span> <span class="o">=</span> <span class="s">"soul.sync.nacos"</span><span class="p">)</span>
    <span class="kd">public</span> <span class="n">NacosConfig</span> <span class="nf">nacosConfig</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="n">NacosConfig</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <h3 id="_5">
      数据初始化与监听
     </h3>
     <p>
      下面我们回过头来看看，我们取消所有的断点，将断点打在下面start函数中，看看watcherData的具体逻辑
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NacosSyncDataService</span> <span class="kd">extends</span> <span class="n">NacosCacheHandler</span> <span class="kd">implements</span> <span class="n">AutoCloseable</span><span class="p">,</span> <span class="n">SyncDataService</span> <span class="p">{</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">start</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">watcherData</span><span class="p">(</span><span class="n">PLUGIN_DATA_ID</span><span class="p">,</span> <span class="k">this</span><span class="p">::</span><span class="n">updatePluginMap</span><span class="p">);</span>
        <span class="n">watcherData</span><span class="p">(</span><span class="n">SELECTOR_DATA_ID</span><span class="p">,</span> <span class="k">this</span><span class="p">::</span><span class="n">updateSelectorMap</span><span class="p">);</span>
        <span class="n">watcherData</span><span class="p">(</span><span class="n">RULE_DATA_ID</span><span class="p">,</span> <span class="k">this</span><span class="p">::</span><span class="n">updateRuleMap</span><span class="p">);</span>
        <span class="n">watcherData</span><span class="p">(</span><span class="n">META_DATA_ID</span><span class="p">,</span> <span class="k">this</span><span class="p">::</span><span class="n">updateMetaDataMap</span><span class="p">);</span>
        <span class="n">watcherData</span><span class="p">(</span><span class="n">AUTH_DATA_ID</span><span class="p">,</span> <span class="k">this</span><span class="p">::</span><span class="n">updateAuthMap</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      打上断点重启后，我们进入到watcherData内部
     </p>
     <p>
      初看我们可能有点懵，一个函数通用调用就监听了我们之前提到的五种数据变化
     </p>
     <p>
      通过跟踪调试，发现其是靠上面函数中的类型和传入的函数进行区分，从而达到一个函数通用调用监听五种数据的，应该是其内部的实现机制
     </p>
     <p>
      大致是根据传入的数据类型id，调用相应的处理函数，这里知晓其大意即可
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NacosCacheHandler</span> <span class="p">{</span>

    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">watcherData</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">dataId</span><span class="p">,</span> <span class="kd">final</span> <span class="n">OnChange</span> <span class="n">oc</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Listener</span> <span class="n">listener</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Listener</span><span class="p">()</span> <span class="p">{</span>
            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="kt">void</span> <span class="nf">receiveConfigInfo</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">configInfo</span><span class="p">)</span> <span class="p">{</span>
                <span class="c1">// 在这里没有发现属性的updatePluginMap之类的</span>
                <span class="n">oc</span><span class="p">.</span><span class="na">change</span><span class="p">(</span><span class="n">configInfo</span><span class="p">);</span>
            <span class="p">}</span>

            <span class="nd">@Override</span>
            <span class="kd">public</span> <span class="n">Executor</span> <span class="nf">getExecutor</span><span class="p">()</span> <span class="p">{</span>
                <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
            <span class="p">}</span>
        <span class="p">};</span>
        <span class="c1">// 这么通过调试，发现触发了初始化操作，从全量的同步一次数据</span>
        <span class="n">oc</span><span class="p">.</span><span class="na">change</span><span class="p">(</span><span class="n">getConfigAndSignListener</span><span class="p">(</span><span class="n">dataId</span><span class="p">,</span> <span class="n">listener</span><span class="p">));</span>
        <span class="c1">// 这里大致看出是添加进监听列表中之类的</span>
        <span class="n">LISTENERS</span><span class="p">.</span><span class="na">getOrDefault</span><span class="p">(</span><span class="n">dataId</span><span class="p">,</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">()).</span><span class="na">add</span><span class="p">(</span><span class="n">listener</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kd">protected</span> <span class="kd">interface</span> <span class="nc">OnChange</span> <span class="p">{</span>
        <span class="kt">void</span> <span class="nf">change</span><span class="p">(</span><span class="n">String</span> <span class="n">changeData</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们在下面的更新插件数据的函数中打上断点，发现在进行第一次启动初始化和修改Admin后台管理界面的插件数据后，都会走到下面的函数：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NacosCacheHandler</span> <span class="p">{</span>

    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">updatePluginMap</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">configInfo</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="c1">// Fix bug #656(https://github.com/dromara/soul/issues/656)</span>
            <span class="n">List</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="n">pluginDataList</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">GsonUtils</span><span class="p">.</span><span class="na">getInstance</span><span class="p">().</span><span class="na">toObjectMap</span><span class="p">(</span><span class="n">configInfo</span><span class="p">,</span> <span class="n">PluginData</span><span class="p">.</span><span class="na">class</span><span class="p">).</span><span class="na">values</span><span class="p">());</span>
            <span class="n">pluginDataList</span><span class="p">.</span><span class="na">forEach</span><span class="p">(</span><span class="n">pluginData</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">pluginDataSubscriber</span><span class="p">).</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">subscriber</span> <span class="o">-&gt;</span> <span class="p">{</span>
                <span class="c1">// 这里就非常的奇怪，先删除一次数据，再更新一次数据</span>
                <span class="n">subscriber</span><span class="p">.</span><span class="na">unSubscribe</span><span class="p">(</span><span class="n">pluginData</span><span class="p">);</span>
                <span class="n">subscriber</span><span class="p">.</span><span class="na">onSubscribe</span><span class="p">(</span><span class="n">pluginData</span><span class="p">);</span>
            <span class="p">}));</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">JsonParseException</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">log</span><span class="p">.</span><span class="na">error</span><span class="p">(</span><span class="s">"sync plugin data have error:"</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      从上面函数大意中，我们大意看出：这是个进行插件信息更新的函数，当收到数据的时候，将其序列化，然后调用相应的subscribe
     </p>
     <p>
      但是我们发现了一个奇怪的逻辑，它先调用删除的逻辑，然后调用更新的逻辑，如前面我们所知，在插件数据进行更新的时候，他们会最终都在走入下面的subscribeDataHandler函数逻辑
     </p>
     <p>
      而进行更新和删除无非就是对本地缓存中的Map进行操作，而在上面的先un再on的操作下，unSubscribe删除数据是不起作用的
     </p>
     <p>
      而且更新数据也不用先删除再添加，直接put替换掉原理的数据即可，不用先删除
     </p>
     <p>
      也就是这个unSubscribe可能是无效和多余的
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CommonPluginDataSubscriber</span> <span class="kd">implements</span> <span class="n">PluginDataSubscriber</span> <span class="p">{</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onSubscribe</span><span class="p">(</span><span class="kd">final</span> <span class="n">PluginData</span> <span class="n">pluginData</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">subscribeDataHandler</span><span class="p">(</span><span class="n">pluginData</span><span class="p">,</span> <span class="n">DataEventTypeEnum</span><span class="p">.</span><span class="na">UPDATE</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">unSubscribe</span><span class="p">(</span><span class="kd">final</span> <span class="n">PluginData</span> <span class="n">pluginData</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">subscribeDataHandler</span><span class="p">(</span><span class="n">pluginData</span><span class="p">,</span> <span class="n">DataEventTypeEnum</span><span class="p">.</span><span class="na">DELETE</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="kt">void</span> <span class="nf">subscribeDataHandler</span><span class="p">(</span><span class="kd">final</span> <span class="n">T</span> <span class="n">classData</span><span class="p">,</span> <span class="kd">final</span> <span class="n">DataEventTypeEnum</span> <span class="n">dataType</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">classData</span><span class="p">).</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">data</span> <span class="o">-&gt;</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">data</span> <span class="k">instanceof</span> <span class="n">PluginData</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">PluginData</span> <span class="n">pluginData</span> <span class="o">=</span> <span class="p">(</span><span class="n">PluginData</span><span class="p">)</span> <span class="n">data</span><span class="p">;</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">dataType</span> <span class="o">==</span> <span class="n">DataEventTypeEnum</span><span class="p">.</span><span class="na">UPDATE</span><span class="p">)</span> <span class="p">{</span>
                    <span class="c1">// 最终都会进行Map的更新操作</span>
                    <span class="n">BaseDataCache</span><span class="p">.</span><span class="na">getInstance</span><span class="p">().</span><span class="na">cachePluginData</span><span class="p">(</span><span class="n">pluginData</span><span class="p">);</span>
                    <span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">handlerMap</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">pluginData</span><span class="p">.</span><span class="na">getName</span><span class="p">())).</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">handler</span> <span class="o">-&gt;</span> <span class="n">handler</span><span class="p">.</span><span class="na">handlerPlugin</span><span class="p">(</span><span class="n">pluginData</span><span class="p">));</span>
                <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">dataType</span> <span class="o">==</span> <span class="n">DataEventTypeEnum</span><span class="p">.</span><span class="na">DELETE</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">BaseDataCache</span><span class="p">.</span><span class="na">getInstance</span><span class="p">().</span><span class="na">removePluginData</span><span class="p">(</span><span class="n">pluginData</span><span class="p">);</span>
                    <span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">handlerMap</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">pluginData</span><span class="p">.</span><span class="na">getName</span><span class="p">())).</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">handler</span> <span class="o">-&gt;</span> <span class="n">handler</span><span class="p">.</span><span class="na">removePlugin</span><span class="p">(</span><span class="n">pluginData</span><span class="p">));</span>
                <span class="p">}</span>
            <span class="k">else</span> <span class="p">{</span>
                <span class="p">......</span>
            <span class="p">}</span>
        <span class="p">});</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      而且通过进一步的Debug和分析，发现Nacos似乎不能监听到数据的删除事件，那就是说如果数据失效没有用了，在Bootstrap没有重启的情况下，这些无效的数据会一直占用内存
     </p>
     <p>
      如果是上面分析的那样，那这个Nacos同步机制就有些问题了
     </p>
     <h2 id="_6">
      总结
     </h2>
     <p>
      这篇大致分析了Nacos的大致数据同步原理，知道同步流程大致如下：
     </p>
     <ul>
      <li>
       NacosSyncDataConfiguration : Nacos启动配置
      </li>
      <li>
       NacosSyncDataService
      </li>
      <li>
       1.初始化读取全量数据进行本地缓存刷新
      </li>
      <li>
       2.启动数据变化监听
      </li>
      <li>
       3.接收变化数据，调用相应的subScribe进行相应的更新
      </li>
     </ul>
     <p>
      同时我们发现可能还存在的问题：
     </p>
     <ul>
      <li>
       1.无法接收到数据删除数据
      </li>
      <li>
       2.在数据变化监听处理函数中，unSubscribe函数可能是无用和多余的
      </li>
     </ul>
    </article>
   </div>
   <!-- Footer -->
   <footer id="footer">
    <p class="copyright">
     © Untitled. Design:
     <a href="https://html5up.net">
      HTML5 UP
     </a>
     .
    </p>
   </footer>
  </div>
  <!-- BG -->
  <div id="bg">
  </div>
  <!-- Scripts -->
  <script src="../assets/js/jquery.min.js">
  </script>
  <script src="../assets/js/browser.min.js">
  </script>
  <script src="../assets/js/breakpoints.min.js">
  </script>
  <script src="../assets/js/util.js">
  </script>
  <script src="../assets/js/main.js">
  </script>
 </body>
</html>
